#pragma once
#include "core/CMath.h"
#include "sigslot/signal.hpp"
#include "Config.h"

/** @note default: OpenGL Camera's position=(0,0,0), direction=(0,0,-1) */
class Camera {
public:
	static void init() { get(); }

	static Camera* get() {
		static auto* camera = new Camera;
		return camera;
	}

	float posX() { return m_position.x; }
	float posY() { return m_position.y; }
	float posZ() { return m_position.z; }
	float pitchAngle() { return m_pitchAngle; }// unit:radian
	float fov() { return m_fov; }// unit:radian
	float yawAngle() { return m_yawAngle; }// unit:radian
	float viewScale() { return m_viewScale; }

	bool setPosX(float x) { if (m_position.x == x) return false; m_position.x = x; setDirty(); return true; }
	bool setPosY(float y) { if (m_position.y == y) return false; m_position.y = y; setDirty(); return true; }
	bool setPosZ(float z) { if (m_position.z == z) return false; m_position.z = z; setDirty(); return true; }
	bool setPitchAngle(float pitchAngle) { auto r = CMath::radians(pitchAngle); if (m_pitchAngle == r) return false; m_pitchAngle = r; setDirty(); return true; }// unit:radian
	bool setFov(float fov) { auto r = CMath::radians(fov); if (m_fov == fov) return false; m_fov = r;  setDirty(); return true; }// unit:radian
	bool setYawAngle(float yawAngle) { auto r = CMath::radians(yawAngle); if (m_yawAngle == r) return false; m_yawAngle = r; setDirty(); return true; }// unit:radian
	bool setViewScale(float viewScale) { if (m_viewScale == viewScale) return false; m_viewScale = viewScale; setDirty(); return true; }

	void resetAll() {
		resetX();
		resetY();
		resetZ();
		resetPitchAngle();
		resetFov();
		resetYawAngle();
		resetViewScale();
	}

	void resetX() { setPosX(Config::Camera_Position.x); }
	void resetY() { setPosY(Config::Camera_Position.y); }
	void resetZ() { setPosZ(Config::Camera_Position.z); }
	void resetPitchAngle() { setPitchAngle(Config::Camera_PitchAngle); }
	void resetFov() { setFov(Config::Camera_FOV); }
	void resetYawAngle() { setYawAngle(Config::Camera_YawAngle); }
	void resetViewScale() { setViewScale(Config::Camera_ViewScale); }

	/**************************** V P VP matrix *************************/
	const Mat& viewMatrix();
	const Mat& projectionMatrix();

	void connectCamChangeSignal(void* zhicheEntity);

	void recalculateIfNeeded();

	const Mat& orthoViewMatrix();
	const Mat& orthoProjectionMatrix();

private:
	Camera();

	sigslot::signal<const Mat&, const Mat&> m_camMatricesSignal;
	sigslot::signal<> m_camChangeSignal;

	void setDirty() { m_isViewMatrixDirty = m_isProjectionMatrixDirty = true; }

	Vec3 m_position = Config::Camera_Position;
	float m_pitchAngle = CMath::radians(Config::Camera_PitchAngle);// radians
	float m_fov = CMath::radians(Config::Camera_FOV);// radians
	float m_yawAngle = CMath::radians(Config::Camera_YawAngle);// radians
	float m_viewScale = Config::Camera_ViewScale;

	Mat m_viewMatrix, m_projectionMatrix;

	bool m_isViewMatrixDirty = true, m_isProjectionMatrixDirty = true;
};